﻿using System;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Web.Mvc;
using System.Web.Routing;
using Supermodel.DomainModel;
using Supermodel.Exceptions;
using Supermodel.MVC;
using Supermodel.MVC.FormModels;
using Supermodel.Reflection;
using Supermodel.Repository;
using Supermodel.UOW;

namespace Supermodel.Controllers
{
    public abstract class ChildCRUDControllerBase<EntityT, ParentEntityT, FormModelT, UnitOfWorkT> : Controller
        where EntityT : ChildEntity<EntityT, ParentEntityT>, new()
        where ParentEntityT : Entity<ParentEntityT>, new()
        where FormModelT : FormModelForChildEntityBase<EntityT, ParentEntityT>, new()
        where UnitOfWorkT : UnitOfWork, new()
    {
        public abstract ActionResult List(long? parentId);

        [HttpDelete]
        public virtual ActionResult Edit(long id, long? parentId, HttpDelete ignore)
        {
            EntityT entityItem = null;
            try
            {
                using (new UnitOfWorkT())
                {
                    entityItem = RepoFactory.Create<EntityT>().GetById(id);
                    entityItem.Delete();
                }
                TempData["message"] = "Deleted";
            }
            catch (Exception)
            {
                UnitOfWorkContext<UnitOfWorkT>.CurrentUnitOfWork.CommitOnDispose = false; //rollback the transaction
                TempData["modalMessage"] = "PROBLEM!!!\\n\\nUnable to delete. Most likely reason: refernces from other entities.";
            }
            return AfterDelete(id, parentId, entityItem);
        }

        [HttpGet]
        public virtual ViewResult Edit(long id, long? parentId, HttpGet ignore)
        {
            using (new UnitOfWorkT())
            {
                if (id == 0) return View(new FormModelT().InitFromDomainModelEntity(new EntityT { ParentId = parentId }));

                var entityItem = RepoFactory.Create<EntityT>().GetById(id);
                var formModelItem = (FormModelT)new FormModelT().InitFromDomainModelEntity(entityItem);

                return View(formModelItem);
            }
        }

        [HttpPost]
        public virtual ActionResult Edit(long id, long? parentId, HttpPost ignore)
        {
            EntityT entityItem;
            using (new UnitOfWorkT())
            {
                if (id == 0) throw new SupermodelException("CRUDControllerBase.Edit[Post]: id == 0");

                entityItem = RepoFactory.Create<EntityT>().GetById(id);
                var formModelItem = (FormModelT)new FormModelT().InitFromDomainModelEntity(entityItem);

                TryUpdateModel(formModelItem);
                if (ModelState.IsValid != true)
                {
                    UnitOfWorkContext<UnitOfWorkT>.CurrentUnitOfWork.CommitOnDispose = false; //rollback the transaction
                    return View(formModelItem);
                }

                formModelItem.UpdateDomainModelEntity(entityItem);

                TempData["message"] = "Saved";
            }
            return AfterUpdate(id, parentId, entityItem);
        }

        [HttpPut]
        public virtual ActionResult Edit(long id, long? parentId, HttpPut ignore)
        {
            EntityT entityItem;
            using (new UnitOfWorkT())
            {
                if (id != 0) throw new SupermodelException("CRUDControllerBase.Edit[Put]: id != 0");

                entityItem = new EntityT { Id = id, ParentId = parentId };
                var formModelItem = (FormModelT)new FormModelT().InitFromDomainModelEntity(entityItem);

                TryUpdateModel(formModelItem);
                if (ModelState.IsValid != true)
                {
                    UnitOfWorkContext<UnitOfWorkT>.CurrentUnitOfWork.CommitOnDispose = false; //rollback the transaction
                    return View(formModelItem);
                }

                formModelItem.UpdateDomainModelEntity(entityItem);
                entityItem.Add();

                TempData["message"] = "Saved";
            }
            return AfterCreate(id, parentId, entityItem);
        }

        [HttpGet]
        public virtual FileContentResult GetBinaryFile(long id, long? parentId, string pn, HttpGet ignore)
        {
            using (new UnitOfWorkT())
            {
                var formModelItem = (FormModelT)new FormModelT().InitFromDomainModelEntity(RepoFactory.Create<EntityT>().GetById(id));
                var file = (BinaryFileFormModel)formModelItem.PropertyGet(pn);
                return File(file.BinaryContent, "application/octet-stream", file.Name);
            }
        }

        [HttpGet]
        public virtual ActionResult DeleteBinaryFile(long id, long? parentId, string pn, HttpDelete ignore)
        {
            EntityT entityItem;
            using (new UnitOfWorkT())
            {
                entityItem = RepoFactory.Create<EntityT>().GetById(id);
                var formModelItem = (FormModelT)new FormModelT().InitFromDomainModelEntity(entityItem);

                //see if pn is a required property
                if (typeof(FormModelT).GetProperty(pn).GetCustomAttributes(typeof(RequiredAttribute), true).Count() == 1)
                {
                    TempData["modalMessage"] = "Cannot delete required field";
                    var routeValues = new RouteValueDictionary(ControllerContext.RouteData.Values);
                    routeValues["Action"] = "Edit";
                    routeValues.Add("parentId", parentId);
                    return RedirectToRoute(routeValues);
                }

                var file = (BinaryFileFormModel)formModelItem.PropertyGet(pn);

                file.Empty();
                formModelItem.UpdateDomainModelEntity(entityItem);
            }
            return AfterBinaryDelete(id, parentId, entityItem);
        }

        #region Protected Methods & Properties
        public virtual ActionResult AfterUpdate(long id, long? parentId, EntityT entityItem)
        {
            return RedirectToAction("List", new { parentId });
        }
        public virtual ActionResult AfterCreate(long id, long? parentId, EntityT entityItem)
        {
            return RedirectToAction("List", new { parentId });
        }
        public virtual ActionResult AfterDelete(long id, long? parentId, EntityT entityItem)
        {
            return RedirectToAction("List", new { parentId });
        }
        public virtual ActionResult AfterBinaryDelete(long id, long? parentId, EntityT entityItem)
        {
            return RedirectToAction("Edit", new { id, parentId });
        }
        #endregion 
    }
}
